<!doctype html>
<html>
	<head>
		<title></title>
		<meta charset='utf-8'>
		<style>
			@keyframes jumping {
				0% {
					transform: scale(2);
				}

				100% {
					transform: scale(1);
				}
			}

			@keyframes dotReveal {
				to {
					opacity: 1;
				}
			}

			@keyframes line {
				0% {
					stroke-dashoffset: 0
				}

				100% {
					stroke-dashoffset: 100%
				}
			}

			body {
				cursor: crosshair;
				background-color: black;
				background-image: linear-gradient(to top, rgba(46, 204, 113, 0.2) 1%, rgba(255, 255, 255, 0) 0),
					linear-gradient(to right, rgba(46, 204, 113, 0.2) 1%, rgba(255, 255, 255, 0) 0);
				background-size: 50px 50px;
			}

			svg,
			html,
			body,
			#app {
				box-sizing: border-box;
				height: 100%;
				width: 100%;
				padding: 0;
				margin: 0;
				overflow: hidden;
			}

			svg {
				position: relative;
				z-index: 1;
			}

			.dot {
				z-index: 10;
				stroke-width: 4;
				stroke: black;
				width: 10px;
				height: 10px;
				fill: rgba(241, 196, 15, 1.0);
				-moz-transform-origin: center;
				-webkit-transform-origin: center;
				transform-origin: center;
				transition: all 0.4s cubic-bezier(0.175, 0.885, 0.320, 1.275);
			}

			.dot--starting.dot--starting {
				fill: black;
				stroke: rgba(46, 204, 113, 1.0);
				stroke-width: 3;
				stroke-miterlimit: outside;
			}

			.dot:not(.dot--starting) {
				opacity: 0;
				animation: dotReveal 1s linear;
				animation-fill-mode: forwards;
			}

			#svg .dot:hover {
				transform: scale(2);
			}

			#svg .dot[data-selected=true] {
				opacity: 1;
				fill: rgba(46, 204, 113, 1.0);
				animation: jumping 0.5s ease-in-out alternate infinite;
				animation-delay: 0;
			}

			#svg .dot[data-visited=true] {
				fill: rgba(46, 204, 113, 1.0);
				animation: jumping 0.5s ease-in-out alternate infinite;
				animation-delay: 0;
				opacity: 1;
			}

			.line {
				z-index: 10;
				stroke: rgba(46, 204, 113, 1.0);
				stroke-width: 2;
				stroke-dasharray: 2 16;
				animation: line 10s linear infinite;
			}

			@media (max-width:480px) {
				#preline {
					display: none;
				}
			}

			#score {
				position: fixed;
				z-index: 0;
				top: 50%;
				left: 50%;
				width: 100%;
				text-align: center;
				transform: translate(-50%, -50%);
				font-size: 20em;
				color: rgba(46, 204, 113, 0.2);
				font-family: 'Bungee Outline', cursive;
			}

			.btn {
				background: transparent;
				cursor: pointer;
				color: rgba(241, 196, 15, 1.0);
				font-weight: bold;
				padding: 15px 60px;
				border-radius: 6px;
				border: 3px solid rgba(241, 196, 15, 1.0);
				transition: all 0.3s ease-out;
			}

			.btn:hover {
				background: rgba(241, 196, 15, 1.0);
				color: black;
			}

			#btn-start {
				position: absolute;
				top: 50%;
				left: 50%;
				transform: translate(-50%, -50%);
			}

			#launch-screen {
				display: flex;
				align-items: center;
				justify-content: center;
				text-align: center;
				visibility: hidden;
				position: absolute;
				z-index: 10;
				background-image: linear-gradient(45deg, black, rgba(46, 204, 113, 0.2));
				top: 0;
				bottom: 0;
				left: 0;
				right: 0;
				opacity: 0;
				transition: all 0.8s cubic-bezier(0.175, 0.885, 0.320, 1.275);
			}

			#launch-screen.is-visible {
				opacity: 1;
				visibility: visible;
			}

			#launch-screen-content {
				position: relative;
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				border-radius: 6px;
				padding: 50px;
				transform: scale(0.8);
				transition: all 0.8s cubic-bezier(0.175, 0.885, 0.320, 1.275);
			}


			#launch-screen.is-visible #launch-screen-content {
				transform: scale(1);
			}

			#launch-screen-content h1 {
				position: relative;
				font-family: 'Bungee Outline', cursive;
				color: rgba(46, 204, 113, 1.0);
				font-size: 4em;
				margin: 0 0 0.4em 0;
			}


			#launch-screen-content p {
				font-family: 'Space Mono', monospace;
				color: #ccc;
				font-size: 1em;
				margin: 0 0 3em 0;
			}

			#lastScore {
				display: none;
			}

			#lastScore.is-visible {
				display: block;
			}

			#lastScore__number {
				color: rgba(231, 76, 60, 1.0);
			}
		</style>
	</head>
	<body>
		<div id="app">
			<div id="score"></div>
			<svg id="svg"></svg>
			<div id="launch-screen" class="is-visible">
				<div id="launch-screen-content">
					<h1 id="launch-screen__title"></h1>
					<p id="launch-screen__description"></p>
					<button class="btn" id="start-btn">PLAY</button>
				</div>
			</div>
		</div>
		<script type="text/javascript">

			// 辅助物体
			function getDistance(obj1, obj2) {
				return Math.floor(
					Math.sqrt(Math.pow(obj1.cx - obj2.cx, 2) + Math.pow(obj1.cy - obj2.cy, 2))
				);
			}
			//计算obj1,obj2 两个对象之间的距离。 使用 Math.sqrt 和 Math.pow 函数计算两个对象之间的平方根。
			function getRandomArbitrary(min, max) {
				return Math.floor(Math.random() * (max - min) + min);
			}//计算随机算

			function comparator(a, b) {
				if (a[1] < b[1]) return -1;
				if (a[1] > b[1]) return 1;
				return 0;
			}
			 //  是一个接受两个参数的函数，source 和 toRemove。
			 // 该函数返回两个数组之间的差异。
			 // 第一行 是一个 if 语句，它检查数组中是否有要从源中删除的值。
			 // 如果源中没有值，那么它将返回一个空列表 ( [] )。
			 // 下一行 是一个 for 循环，它遍历 source 中的每个值，并通过对照 -1 检查它们的索引位置将它们从 toRemove 中删除。
			 // 该 将返回另一个中不存在的 a 和 b 的值。
			 // 该 将返回另一个中不存在的 a 和 b 的值。
			function difference(source, toRemove) {
				return source.filter(function(value) {
					return toRemove.indexOf(value) == -1;
				});
			}
			
			var svg = document.getElementById("svg");
			var dotMatrix = document.createElementNS(
				"http://www.w3.org/2000/svg",
				"circle"
			);
			var lineMatrix = document.createElementNS("http://www.w3.org/2000/svg", "line");
			var screenW = window.innerWidth;
			var screenH = window.innerHeight;
			var totalDist = document.getElementById("distance");

			// 线的构造函数
			function Line(x1, y1, x2, y2) {
				this.x1 = x1;
				this.y1 = y1;
				this.x2 = x2;
				this.y2 = y2;
				// 声明一个新的类有四个参数是线上起点的坐标 x2 和 y2，代表它在屏幕上的终点。
				this.el = document.createElementNS("http://www.w3.org/2000/svg", "line");
				this.class = "line";
				this.update = function(x1, y1, x2, y2) {
					 // update() 更新 Line 的 el 属性的所有四个属性，
					this.el.setAttribute("x1", x1 || this.x1);
					this.el.setAttribute("y1", y1 || this.y1);
					this.el.setAttribute("x2", x2 || this.x2);
					this.el.setAttribute("y2", y2 || this.y2);
					this.setAttr("class", this.class);
				};
				this.setAttr = function(attr, value) {
					this.el.setAttribute(attr, value);
				};
				this.append = function() {
					svg.insertBefore(this.el, svg.firstChild);
					
				};
				// append() 将 Line 附加到 svg该 是一个创建 SVG Line 对象的函数。
				//  然后该函数将 x1、y1、x2 和 y2 属性分配给 Line 对象。
				//  当以下任一情况发生时，将调用 Line 对象的 update() 方法： - 在 Line 对象上调用 setAttr("x1", "x2") 时。
				//  在这种情况下，它会将这些值分别分配给 el 属性的 x1 和 x2 属性。
				//  - 在 Line 对象上调用 setAttr("y1", "y2") 时。
				//  在这种情况下，它会将这些值分别分配给 el 属性的 y1 和 y2 属性。
			}

			//点构造器
			function Dot(r, cx, cy) {
				this.r = r;
				this.cx = cx;
				this.cy = cy;
				this.el = document.createElementNS("http://www.w3.org/2000/svg", "circle");
				this.class = "dot";
				this.update = function() {
					this.el.setAttribute("r", this.r);
					this.el.setAttribute("cx", this.cx);
					this.el.setAttribute("cy", this.cy);
					this.setAttr("class", this.class);
				};

				// 激活一个点用于存储游戏中每个点的所有数据的对象。
				this.activate = function() {
					for (i = 0; i < dots.num; i++) {
						dots.list[i].setAttr("data-selected", "false");
					}
					this.setAttr("data-selected", "true");
					// 将所有其他点的 selected 属性设置为 false，因此它们未被选中且无法单击。
					// 它将这个点的 selected 属性设置为 true，以便它现在可以选择并且可以单击。
				};

				this.visited = function() {
					this.setAttr("data-visited", "true");
					// 创建一个新的函数 单击点进行访问，执行函数
					// 在这种情况下，它将其自己的访问属性设置为 true，以便我们知道哪些点之前已访问过，
					// 哪些点在我们当前与游戏的会话期间尚未访问过。
				};

				// 将属性设置为元素
				this.setAttr = function(attr, value) {
					this.el.setAttribute(attr, value);
				};

				// 获取元素的属性。
				this.getAttr = function(attr) {
					return this.el.getAttribute(attr);
				};

				// 元素追加到SVG并附加事件监听器
				this.append = function() {
					svg.appendChild(this.el);
					this.el.addEventListener("click", this.onClick);
				};

				// 点击点元素
				this.onClick = function(event) {
					//gets the id and the coords of the dot
					var thisId = Number(event.target.getAttribute("data-id").substr(3, 2));
					var thisCx = dots.list[thisId].cx;
					var thisCy = dots.list[thisId].cy;

					// 计算点之间的距离
					var distances = [];
					for (i = 0; i < dots.num; i++) {
						distances[i] = [i, getDistance(dots.selected, dots.list[i])];
					}
					distances.sort(comparator);
					distances.splice(0, 1);
					var distancesLeft = [];
					for (i = 0; i < distances.length; i++) {
						if (dots.left.includes(distances[i][0])) {
							distancesLeft.push(distances[i][0]);
						}
					}

					//如果元素是最近的
					if (thisId == distancesLeft[0] && dots.left.includes(thisId)) {
						// 计算距离
						var newDistance = getDistance(dots.list[thisId], dots.selected);

						app.score.update(1); // punteggio x numero di poi
						// app.score.update(newDistance); punteggio x distanza

						//设置为选定的点
						dots.list[thisId].activate();
						dots.list[thisId].visited();

						// 创建线
						lines.list.push(
							new Line(
								dots.selected.cx,
								dots.selected.cy,
								dots.list[thisId].cx,
								dots.list[thisId].cy
							)
						);
						lines.list[lines.list.length - 1].update();
						lines.list[lines.list.length - 1].append();

						// 创建预览行
						//除去这些，你就能活下去了

						svg.addEventListener("mousemove", function prelineMove(e) {
							mouseX = e.pageX;
							mouseY = e.pageY;
							app.preline.update(thisCx, thisCy, mouseX, mouseY);
						});

						//保存选定的点坐标
						dots.selected.id = thisId;
						dots.selected.cx = thisCx;
						dots.selected.cy = thisCy;

						//将圆点从剩余的圆点列表中移除
						for (i = 0; i < dots.left.length; i++) {
							if (dots.left[i] === thisId) {
								dots.left.splice(i, 1);
							}
						}

						if (dots.left.length == 0) {
							app.end(true);
						}
					} else {
						app.end(false);
					}
				};
			}

			// 线路组、
			var lines = {
				list: []
			};
			// dots group	
			var dots = {};
			dots.num = 20;
			dots.list = [];
			dots.start = 0;
			dots.selected = {};
			dots.selected.id = dots.start;
			dots.left = [];
			dots.preline;

			// 计算
			var app = {};
			app.level = 4;//关卡
			app.score = {};//分数
			app.score.number = 0;//分数为0
			app.score.el = document.getElementById("score");
			app.score.update = function(score) {
				//计算得分
				app.score.number += score;
				app.score.el.textContent = app.score.number;
			};
			//重置按钮分数清零
			app.score.reset = function() {
				app.score.number = 0;
				app.score.update(0);
			};

			app.results = function(points) {
				if (points == "reset") {
					sessionStorage.setItem("results", 0);
				} else {
					if (!sessionStorage.getItem("results")) {
						sessionStorage.setItem("results", points);
					} else {
						var newscore = parseInt(sessionStorage.getItem("results")) + points;
						sessionStorage.setItem("results", newscore);
					}
				}
			};

			app.launchScreen = function(lastScore, title, description, btnText) {
				//  创建一个名为 launchScreen 的函数，该函数将用于创建应用程序的一个实例并将其显示在屏幕上。
				app.launchScreen.el = document.getElementById("launch-screen");
				// 使用 document.getElementById() 来获取对启动屏幕的标题、描述和 btnText 元素的引用。
				app.launchScreen.el.setAttribute("class", "is-visible");
				var launchScreenTitle = document.getElementById("launch-screen__title");
				launchScreenTitle.textContent = title;
				var launchScreenDescription = document.getElementById(
					"launch-screen__description"
				);
				launchScreenDescription.textContent = description;
				app.launchScreen.btn = document.getElementById("start-btn");
				// 当用户单击 start-btn 或 stop-btn 以分别启动或关闭此屏幕时设置事件侦听器。
				//   会将launchScreen 的class 属性设置为“is-visible”，使其在屏幕上可见。
				app.launchScreen.btn.textContent = btnText;
				app.launchScreen.btn.addEventListener("click", function lauch() {
					app.launchScreen.el.setAttribute("class", "");
					app.start(app.level);
					app.launchScreen.btn.removeEventListener("click", lauch);
				});
			};
			app.preline = new Line(0, 0, 200, 200);
			app.preline.setAttr("id", "preline");
			// 首先设置要在屏幕上绘制的点数。
			//  然后它遍历所有点并为每个点创建一个新点。
			//  然后它为那个点设置一些属性，比如它的位置、大小、动画延迟、变换原点等。
			
			//   是动画的起点。
			app.start = function(dotsNum) {
				dots.num = dotsNum;// 将dotsNum 变量设置为一个数字
				for (i = 0; i < dots.num; i++) {
					var cx = getRandomArbitrary(10, screenW - 10);
					var cy = getRandomArbitrary(10, screenH - 10);
					dots.list[i] = new Dot(8, cx, cy);
					dots.list[i].setAttr("data-id", "id-" + i);
					dots.list[i].setAttr(
						"style",
						"animation-delay:" + i / 10 + "s; transform-origin: " + cx + 'px ' + cy + 'px;');
					dots.list[i].update();
					dots.list[i].append();
					dots.left.push(i);
					if (i == dots.start) {
						dots.selected.cx = dots.list[dots.start].cx;
						dots.selected.cy = dots.list[dots.start].cy;
						dots.list[dots.start].setAttr("class", "dot dot--starting");
						dots.left.splice(i, 1);
					}
					// 设置了一个id属性，style，animation-delay:10s；变换原点：cx+cy；对于每个点。
				 // 然后它更新它们并将它们附加到左侧的点列表中（它们将显示在哪里）。
					// 添加preline
					app.preline.update(
						dots.selected.cx,
						dots.selected.cy,
						dots.selected.cx,
						dots.selected.cy
					);
					 // preline 功能，当您将鼠标移到 cx 或 cy 值相对于其父位置（起点）的位置在 0 到 10 像素之间的任何点上时，该功能将更新。
					app.preline.append();// 在屏幕上创建随机数量的点，然后将它们附加到数组中。
					svg.addEventListener("mousemove", function prelineMove(e) {
						// 一个事件侦听器，用于侦听 mousemove 事件并相应地更新点的位置。
						mouseX = e.pageX;
						mouseY = e.pageY;
						app.preline.update(dots.selected.cx, dots.selected.cy, mouseX, mouseY);
					});
				}
				// 它到达起始点时，它将 data-selected 属性设置为 true
				dots.list[dots.start].setAttr("data-selected", "true");
			};

			app.end = function(win) {
				if (win) {
					app.level += 4;
					app.results(app.score.number);
				} else {
					app.level = 4;
				}

				dots.list = [];
				dots.selected = {};
				dots.left.length = 0;
				svg.innerHTML = "";

				if (win) {
					app.launchScreen(
						app.score.number,
						"非常棒!",
						"你的分数是: " + sessionStorage.getItem("results") + ' 下一级会更难哦！',
						"继续"
					);
					
				} else {
					app.launchScreen(
						0,
						"游戏结束!",
						"你的最终得分是: " + sessionStorage.getItem("results"),
						"再来一局"
					);
					app.results("reset");
					app.score.reset();
				}
			};

			app.launchScreen(
				0,
				"最短距离",
				"寻找最近的点.",
				"开始"
			);
		</script>
	</body>
</html>
